package com.cloudbroker.bcs.common.configure;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.TreeMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import com.cloudbroker.bcs.common.configure.exception.NoBrokerConfigException;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.cloudbroker.bcs.common.configure.constants.ConfigureConstants;
import com.cloudbroker.bcs.common.configure.model.TenantInfo;
import com.cloudbroker.bcs.common.context.DataContext;
import com.cloudbroker.bcs.common.util.EnvironmentUtils;
import com.cloudbroker.bcs.common.util.IOUtil;

public class ConfigUtil {
    
    private static final Logger LOGGER = LoggerFactory.getLogger(ConfigUtil.class);
    
    private static List<String> fileList;
    
    private static HSBCSZooKeeperConfigurator zkConfigurator;
    
    private static Map<String, TenantConfig> tenantConfigMap = new TreeMap<String, TenantConfig>();
    
    private static TenantConfig noTenantConfig = new TenantConfig(ConfigureConstants.TENANT_ID_NO_TENANT);

    private static Lock tenantsLock = new ReentrantLock();
    
    private static ConfigUtil instance = new ConfigUtil();
    
    public static HSBCSZooKeeperConfigurator getZkConfigurator() {
        return zkConfigurator;
    }
    
    private ConfigUtil() {
    }
    
    public static ConfigUtil getInstance() {
        return instance;
    }
    
    public static Map<String, Object> getProp() {
        return getTenantConfig().getAllInOneConfig();
    }
    
    public static Map<String, Object> getProp(String tenantId) {
        return getTenantConfig(tenantId).getAllInOneConfig();
    }
    
    public static String getString(String key, String defaultValue) {
        return getTenantConfig().get(key, defaultValue);
    }
    
    public static String getString(String key) {
        return getTenantConfig().getString(key, "");
    }

    public static String getString(String tenantId,String key,String defaultValue) {
        return getTenantConfig(tenantId).getString(key,defaultValue);
    }
    
    public static int getInt(String key, int defaultValue) {
        return getTenantConfig().getInt(key, defaultValue);
    }

    public static int getInt(String tenantId,String key, int defaultValue) {
        return getTenantConfig(tenantId).getInt(key,defaultValue);
    }
    
    public static long getLong(String key, long defaultValue) {
        return getTenantConfig().getLong(key, defaultValue);
    }

    public static long getLong(String tenantId,String key, long defaultValue) {
        return getTenantConfig(tenantId).getLong(key, defaultValue);
    }
    public static boolean getBoolean(String key, boolean defaultValue) {
        return getTenantConfig().getBoolean(key, defaultValue);
    }

    public static boolean getBoolean(String tenantId,String key, boolean defaultValue) {
        return getTenantConfig(tenantId).getBoolean(key, defaultValue);
    }
    
    public static Double getDouble(String key, double defaultValue) {
        return getTenantConfig().getDouble(key, defaultValue);
    }

    public static Double getDouble(String tenantId,String key, double defaultValue) {
        return getTenantConfig(tenantId).getDouble(key, defaultValue);
    }
    
    public static String get(String key) {
        return getTenantConfig().get(key);
    }

    public static String get(String tenantId,String key, String defaultValue) {
        return getTenantConfig(tenantId).get(key,defaultValue);
    }
    
    public static String get(String key, String defaultValue) {
        return getTenantConfig().get(key, defaultValue);
    }
    
    public static String getWithFormat(String key, Object... vars) {
        return getTenantConfig().getWithFormat(key, vars);
    }
    
    public static Integer get(String key, Integer defaultValue) {
        return getTenantConfig().get(key, defaultValue);
    }
    
    public static Long get(String key, Long defaultValue) {
        return getTenantConfig().get(key, defaultValue);
    }
    
    public static Boolean get(String key, Boolean defaultValue) {
        return getTenantConfig().get(key, defaultValue);
    }
    
    public static Double get(String key, Double defaultValue) {
        return getTenantConfig().get(key, defaultValue);
    }
    
    public static boolean isNoTenant(String tenantId) {
        return StringUtils.isBlank(tenantId) || ConfigureConstants.TENANT_ID_NO_TENANT.equals(tenantId);
    }
    
    public static boolean isNoTenant() {
        return isNoTenant(DataContext.getTenantId());
    }
    
    public static Collection<String> getTenantIds() {
        try {
            tenantsLock.lock();
            return Collections.unmodifiableCollection(new ArrayList<String>(tenantConfigMap.keySet()));
        } finally {
            tenantsLock.unlock();
        }
    }
    
    public static List<TenantInfo> getTenantInfos() {
        Collection<String> tenantIds = getTenantIds();
        List<TenantInfo> tenantInfos = new ArrayList<TenantInfo>(tenantIds.size());
        for (String tenantId : tenantIds) {
            TenantConfig tenantConfig = tenantConfigMap.get(tenantId);
            if (null != tenantConfig) {
                tenantInfos.add(tenantConfig.getTenantInfo());
            }
        }
        return tenantInfos;
    }
    
    public static String getNamedTenantId() {
        String brokerId = DataContext.getBrokerId();
        if (StringUtils.isBlank(brokerId)) {
            brokerId = ConfigureConstants.TENANT_ID_NO_TENANT;
        }
        return brokerId;
    }
    
    public static TenantConfig getTenantConfig() {
        String tenantId = getNamedTenantId();
        return getTenantConfig(tenantId);
    }
    
    public static TenantConfig getTenantConfig(String tenantId) {
        TenantConfig config = getTenantConfig(tenantId, false);
        if (config == null && !ConfigureConstants.TENANT_ID_NO_TENANT.equals(tenantId)) {
            return getTenantConfig(ConfigureConstants.TENANT_ID_NO_TENANT, false);
        }
        return config;
    }
    
    public static TenantConfig getNoTenantConfig() {
        return noTenantConfig;
    }
    
    public static void removeTenantConfig(String tenantId) {
        if (tenantConfigMap.containsKey(tenantId)) {
            boolean removed = false;
            try {
                tenantsLock.lock();
                removed = null != tenantConfigMap.remove(tenantId);
            } finally {
                tenantsLock.unlock();
            }
            if (removed) {
                LOGGER.info("TenantConfig removed: " + tenantId);
            }
        }
    }
    
    static TenantConfig getTenantConfig(String tenantId, boolean checkTenantConfiged) {
        if (isNoTenant(tenantId)) {
            return noTenantConfig;
        }
        boolean newAdded = false;
        TenantConfig config = tenantConfigMap.get(tenantId);
        if (null != config) {
            config.putTenantConfig(noTenantConfig);
        }
        if (null == config) {
            try {
                tenantsLock.lock();
                config = tenantConfigMap.get(tenantId);
                if (null == config) {
                    if (StringUtils.isEmpty(tenantId)) {
                        // 默认空白配置/无配置
                        config = new TenantConfig("");
                    } else {
                        if (null == zkConfigurator) {
                            config = createTenantConfigWithFile(tenantId, checkTenantConfiged);
                        } else {
                            config = createTenantConfigWithZooKeeper(tenantId, checkTenantConfiged);
                        }
                    }
                    if (null != config) {
                        tenantConfigMap.put(tenantId, config);
                    } else if (checkTenantConfiged) {
                        throw new NoBrokerConfigException("undefined broker in configure info : " + tenantId);
                    }
                }
            } finally {
                tenantsLock.unlock();
            }
        }
        if (newAdded) {
            LOGGER.info("TenantConfig added: " + tenantId);
        }
        return config;
    }
    
    private static TenantConfig createTenantConfigWithZooKeeper(String tenantId, boolean checkTenantConfiged) {
        if (checkTenantConfiged) {
            throw new NoBrokerConfigException("undefined broker in configure info : " + tenantId);
        } else {
            TenantConfig config = new TenantConfig(tenantId);
            return config;
        }
    }
    
    private static TenantConfig createTenantConfigWithFile(String tenantId, boolean checkTenantConfiged) {
        TenantConfig config = new TenantConfig(tenantId);
        Properties prop = loadPropertiesFromFiles(tenantId);
        initByProperties(config, prop);
        return config;
    }
    
    public static String getFileNameWithTenant(String fileName) {
        String tenantId = DataContext.getTenantId();
        if (!isNoTenant(tenantId)) {
            int idx = fileName.lastIndexOf('/');
            if (-1 != idx) {
                return new StringBuilder().append(fileName.substring(0, idx)).append('/').append(tenantId)
                        .append(fileName.substring(idx)).toString();
            }
        }
        return fileName;
    }
    
    private static Properties loadPropertiesFromFiles(String tenantId) {
        Properties prop = new Properties();
        if (fileList != null) {
            for (String fileName : fileList) {
                String targetFilePath = getFileNameWithTenant(fileName);
                
                InputStream configIs = null;
                try {
                    String filePath = EnvironmentUtils.findFileInRuntimeConfigDirOrClasspath(targetFilePath);
                    if (null == filePath) {
                        LOGGER.warn("config file not exists: " + fileName);
                        continue;
                    }
                    
                    configIs = new FileInputStream(filePath);
                    prop.load(configIs);
                } catch (IOException e) {
                    LOGGER.error("读取配置文件错误" + e.toString(), e);
                } finally {
                    IOUtil.close(configIs);
                }
            }
        }
        return prop;
    }
    
    public static void initNoTenantConfigFromFiles() {
        Properties properties = loadPropertiesFromFiles(ConfigureConstants.TENANT_ID_NO_TENANT);
        initByProperties(noTenantConfig, properties);
    }
    
    private static void initByProperties(TenantConfig tenantConfig, Properties properties) {
        Map<String, Object> allInOneConfig = tenantConfig.getAllInOneConfig();
        for (Entry<Object, Object> entry : properties.entrySet()) {
            allInOneConfig.put((String) entry.getKey(), entry.getValue());
        }
    }
    
    public static void setFileList(List<String> fileList) {
        ConfigUtil.fileList = fileList;
    }
    
    public static void setZkConfigurator(HSBCSZooKeeperConfigurator zkConfigurator) {
        ConfigUtil.zkConfigurator = zkConfigurator;
        if (null != zkConfigurator) {
            LOGGER.info("zkConfigurator setted, will use configuration on zookeeper");
        }
    }

    public static void deleteConfig(String tenantId) {
        if( Thread.currentThread().getStackTrace()[2].getMethodName().equals("deleteConfig") ) {
            try {
                tenantsLock.lock();
                tenantConfigMap.remove(tenantId);
            } finally {
                tenantsLock.unlock();
            }
        }
    }
}
